/*
    Linear Programming Library
    
    Created by Team Paragon (Shena Hoffmann, Arthur Powalka, Nasir Kenarangui)
    CSCE 689
    
    LinProg.cpp
    CPP file containing definitions for the LinProg class (as declared in LinProg.h) for storing and 
    	taining linear programming problems. Additionally, contains methods for conversion to different 
    linear programming formats and  multiple algorithms for solving linear programs.
*/

#include "LinProg.h"
    
// --------- CONSTRUCTORS ---------

//constructor to create an empty LinProg
LinProg::LinProg()
{
    //creates an empty LinProg
}

//copy constructor
LinProg::LinProg(const LinProg& lp)
{
    max = lp.max;
    objective = lp.objective;
    constraints = lp.constraints;
    inequalitySigns = lp.inequalitySigns;
    inequalityVals = lp.inequalityVals;
    constrainedVars = lp.constrainedVars;
    
    //error checking on variables
    verifyLinProg();    //will throw an exception if something is wrong
}

LinProg::LinProg(bool max, vector<double> objective, vector<vector<double>> constraints, vector<string> inequalitySigns, vector<double> inequalityVals, vector<bool> constrainedVars)
{
    this->max = max;
    this->objective = objective;
    this->constraints = constraints;
    this->inequalitySigns = inequalitySigns;
    this->inequalityVals = inequalityVals;
    this->constrainedVars = constrainedVars;
    
    //error checking on variables
    verifyLinProg();
}

// --------- DESTRUCTORS ---------

LinProg::~LinProg()
{
    //destruct each of the variables
    /*objective.~vector();
    constraints.~vector();
    inequalitySigns.~vector();
    inequalityVals.~vector();
    constrainedVars.~vector();*/
}

// --------- RETRIEVAL FUNCTIONS ---------

bool LinProg::isMaximum()
{
    return max;
}

vector<double> LinProg::getObjective()
{
    return objective;
}

vector<vector<double>> LinProg::getConstraints()
{
    return constraints;
}

vector<string> LinProg::getInequalitySigns()
{
    return inequalitySigns;
}

vector<double> LinProg::getInequalityVals()
{
    return inequalityVals;
}

vector<bool> LinProg::getConstrainedVars()
{
    return constrainedVars;
}

// --------- MODIFIER FUNCTIONS ---------

void LinProg::setMaximum(bool isMax)
{
    max = isMax;
}

void LinProg::setObjective(vector<double> newObjective)
{
    objective = newObjective;
}

void LinProg::setConstraints(vector<vector<double>> newConstraints)
{
    //error checking: each constraint equation should have the same number of coefficients
    int constraintSize = newConstraints[0].size();
    for (int i = 0; i < newConstraints.size(); i++)
    {
        if (newConstraints[i].size() != constraintSize)
        {
            throw  InvalidCoeffsException("constraints");
        }
    }
    
    constraints = newConstraints;
}

void LinProg::setInequalitySigns(vector<string> newInequalitySigns)
{
    //error checking: make sure the provided vector contains only expected strings (<=, >=, or =)
    for (int i = 0; i < newInequalitySigns.size(); i++)
    {
        if (!(newInequalitySigns[i] == "<=" || newInequalitySigns[i] == ">=" || newInequalitySigns[i] == "="))
        {
            throw InvalidInequalityException();
        }
    }

    inequalitySigns = newInequalitySigns;
}

void LinProg::setInequalityVals(vector<double> newInequalityVals)
{
    inequalityVals = newInequalityVals;
}

void LinProg::setConstrainedVars(vector<bool> newConstrainedVars)
{
    constrainedVars = newConstrainedVars;
}

// --------- INFORMATIVE FUNCTIONS ---------
bool LinProg::isStandardForm()
{
    //check if maximum
    if (!max) return false;
    
    //check that all constraint equations are <=
    for (int i = 0; i < inequalitySigns.size(); i++)
    {
        if (inequalitySigns[i] != "<=")
            return false;
    }
    
    //check that all variables are constrained
    for (int i = 0; i < constrainedVars.size(); i++)
    {
        if (constrainedVars[i] == false)
            return false;
    }
    
    return true;
}

bool LinProg::isDualForm()
{
    //check if minimum
    if (max) return false;
    
    //check that all constraint equations are <=
    for (int i = 0; i < inequalitySigns.size(); i++)
    {
        if (inequalitySigns[i] != ">=")
            return false;
    }
    
    //check that all variables are constrained
    for (int i = 0; i < constrainedVars.size(); i++)
    {
        if (constrainedVars[i] == false)
            return false;
    }
    
    return true;
}

//verify that the LinProg values are correct
bool LinProg::verifyLinProg()
{
    //check that constraints.size() == inequalitySigns.size()
    if (constraints.size() != inequalitySigns.size())
    {
        //throw an exception
        throw InvalidConstraintsException("signs");
        return false;
    }
    
    //check that constraints.size() == inequalityVals.size()
    if (constraints.size() != inequalityVals.size())
    {
        //throw an exception
        throw InvalidConstraintsException("vals");
        return false;
    }

    //check that objective.size() == constraints[i].size() and that each of the constraint functions is the same size
    for (int i = 0; i < constraints.size(); i++)
    {
        if (i > 0)
        {
            if (constraints[i].size() != constraints[i-1].size())
            {
                throw InvalidCoeffsException("constraints");
                return false;
            }
        }
        if (objective.size() != constraints[i].size())
        {
            //throw an exception
            throw InvalidCoeffsException("objective");
            return false;
        }
    }
    
    //check that constrainedVars.size() == constraints[i].size() == objective.size()
    if (constrainedVars.size() != objective.size())
    {
        //throw an exception
        throw InvalidCoeffsException("vars");
        return false;
    }
    
    //check that inequalitySigns == "<=", ">=", or "="
    for (int i = 0; i < inequalitySigns.size(); i++)
    {
        if (!(inequalitySigns[i] == "<=" || inequalitySigns[i] == ">=" || inequalitySigns[i] == "="))
        {
            //throw an InvalidValues exception
            throw InvalidInequalityException();
            return false;
        }
    }
    
    return true;
}

// --------- LINEAR PROGRAM CONVERSION FUNCTIONS ---------

LinProg LinProg::convertToStandardForm()
{
    verifyLinProg();
    
    //if already in standard form, return the current object
    if (isStandardForm())
        return *this;
    
    LinProg standard;
    if (!max)
    {
        //set standard objective == to the negative of this objective
        for (int i = 0; i < objective.size(); i++)
        {
            standard.objective.push_back(objective[i]*(-1));
        }
    }
    else
    {
        standard.objective = objective;
    }
    standard.max = true;
    
    //convert all constraint functions to <=
    for ( int i = 0; i < inequalitySigns.size(); i++)
    {
        if (inequalitySigns[i] == "<=")
        {
            standard.inequalitySigns.push_back(inequalitySigns[i]);
            standard.constraints.push_back(constraints[i]);
            standard.inequalityVals.push_back(inequalityVals[i]);
        }
        else if (inequalitySigns[i] == "=")
        {
            //replace with one function with >= and one function with <=
            standard.inequalitySigns.push_back("<=");
            standard.constraints.push_back(constraints[i]);
            standard.inequalityVals.push_back(inequalityVals[i]);
            
            standard.inequalitySigns.push_back("<=");
            vector<double> negativeConstraint;
            for (int j = 0; j < constraints[i].size(); j++)
            {
                negativeConstraint.push_back(constraints[i][j]*(-1));
            }
            standard.constraints.push_back(negativeConstraint);
            standard.inequalityVals.push_back(inequalityVals[i]*(-1));
        }
        else if (inequalitySigns[i] == ">=")
        {
            //multiply constraint by -1
            standard.inequalitySigns.push_back("<=");
            vector<double> negativeConstraint;
            for (int j = 0; j < constraints[i].size(); j++)
            {
                negativeConstraint.push_back(constraints[i][j]*(-1));
            }
            standard.constraints.push_back(negativeConstraint);
            standard.inequalityVals.push_back((-1)*inequalityVals[i]);
        }
    } 

    //make sure all variables are constrained - if not, replace each var x_j w/ (x_j' - x_j'') and add x_j' >=0 and x_j'' >= 0
    for (int i = 0; i < constrainedVars.size(); i++)
    {
        if (constrainedVars[i] == false)
        {
            //replace each var with x_j w/ (x_j' - x_j'')
            //just subtract the other var, and keep the original one
            for (int j = 0; j < standard.constraints.size(); j++)
            {
                standard.constraints[j].push_back(standard.constraints[j][i]*(-1));
            }
            
            //add variable to objective function
	    //standard.objective.push_back(0);
            standard.objective.push_back(objective[i]*(-1)); //this fixes convertToStandardFrom
            
            //set the variable to be true in constrainedVars
            standard.constrainedVars.push_back(true);
        }
        standard.constrainedVars.push_back(true);
    }
    return standard;
}

LinProg LinProg::convertToDualForm()
{
    verifyLinProg();
    
    LinProg dual;
    
    //if not already in standard form, put it into standard form before getting the dual
    if (!isStandardForm())
    {
        LinProg stdForm;
        stdForm = convertToStandardForm();
        
        dual.setMaximum(false);
        dual.setObjective(stdForm.inequalityVals);
        vector<vector<double>> dualConstraints;
        for (int j = 0; j < stdForm.constraints[0].size(); j++)
        {
            vector<double> eq;
            for (int k = 0; k < stdForm.constraints.size(); k++)
            {
                eq.push_back(stdForm.constraints[k][j]);
            }
            dualConstraints.push_back(eq);
        }
        dual.setConstraints(dualConstraints);
        dual.setInequalityVals(stdForm.objective);
        for (int i = 0; i < stdForm.constraints.size(); i++)
        {
            dual.inequalitySigns.push_back(">=");
        }
        
        vector<bool> constrained;
        for (int i = 0; i < dual.getConstraints()[0].size(); i++)
        {
            constrained.push_back(true);
        }
        dual.setConstrainedVars(constrained);
    }
    else 
    {
        dual.setMaximum(false);
        dual.setObjective(inequalityVals);
        vector<vector<double>> dualConstraints;
        for (int j = 0; j < constraints[0].size(); j++)
        {
            vector<double> eq;
            for (int k = 0; k < constraints.size(); k++)
            {
                eq.push_back(constraints[k][j]);
            }
            dualConstraints.push_back(eq);
        }
        dual.setConstraints(dualConstraints);
        dual.setInequalityVals(objective);
        for (int i = 0; i < constraints.size(); i++)
        {
            dual.inequalitySigns.push_back(">=");
        }
        
        vector<bool> constrained;
        for (int i = 0; i < dual.getConstraints()[0].size(); i++)
        {
            constrained.push_back(true);
        }
        dual.setConstrainedVars(constrained);
    }
    
    return dual;
}

// --------- PRINTING FUNCTIONS ---------

void LinProg::printLinProg()
{
    string variable;
    if (max)
    {
        cout << "Maximize ";
        variable = "x_";
    }
    else
    {
        cout << "Minimize ";
        variable = "y_";
    }
    for (int i = 0; i < objective.size(); i++)
    {
        if (i > 0) cout << " + " << objective[i] << variable << i+1;
        else cout << objective[i] << variable << i+1;
    }
    
    cout << endl << "Subject to:" << endl;
    
    for (int i = 0; i < constraints.size(); i++)
    {
        for (int j = 0; j < constraints[i].size(); j++)
        {
            if ( j > 0) cout << " + " << constraints[i][j] << variable << j+1;
            else cout << constraints[i][j] << variable << j+1;
        }
        cout << " " << inequalitySigns[i] << " " << inequalityVals[i] << endl;
    }
}

// --------- OVERLOADED OPERATORS ---------
bool LinProg::operator==(const LinProg &other) const 
{
    //check max
    if (max != other.max)
        return false;
    
    //check objective
    if (objective.size() != other.objective.size())
        return false;
    else
    {
        for (int i = 0; i < objective.size(); i++)
        {
            if (objective[i] != other.objective[i])
                return false;
        }
    }
    
    //check constraints
    if (constraints.size() != other.constraints.size())
        return false;
    else
    {
        for (int i = 0; i < constraints.size(); i++)
        {
            if (constraints[i].size() != other.constraints[i].size())
                return false;
            else
            {
                for (int j = 0; j < constraints[i].size(); j++)
                {
                    if (constraints[i][j] != other.constraints[i][j])
                        return false;
                }
            }
        }
    }
    
    //check inequalitySigns
    if (inequalitySigns.size() != other.inequalitySigns.size())
        return false;
    else 
    {
        for (int i = 0; i < inequalitySigns.size(); i++)
        {
            if (inequalitySigns[i] != other.inequalitySigns[i])
                return false;
        }
    }
    
    //check inequalityVals
    if (inequalityVals.size() != other.inequalityVals.size())
        return false;
    else 
    {
        for (int i = 0; i < inequalityVals.size(); i++)
        {
            if (inequalityVals[i] != other.inequalityVals[i])
                return false;
        }
    }
    
    //check constraintVars
    if (constrainedVars.size() != other.constrainedVars.size())
        return false;
    else 
    {
        for (int i = 0; i < constrainedVars.size(); i++)
        {
            if (constrainedVars[i] != other.constrainedVars[i])
                return false;
        }
    }
} 

bool LinProg::operator!=(const LinProg &other) const 
{
    return !(*this == other);
}
